package com.zhx.common.user.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.zhx.common.common.domain.vo.resp.ApiResult;
import com.zhx.common.user.dao.UserDao;
import com.zhx.common.user.domain.entity.IpDetail;
import com.zhx.common.user.domain.entity.IpInfo;
import com.zhx.common.user.domain.entity.User;
import com.zhx.common.user.service.IpService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.security.auth.DestroyFailedException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class IpServiceImpl implements IpService, DisposableBean {

    @Resource
    private UserDao userDao;

    private static ExecutorService executor = new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(500), new NamedThreadFactory("refresh-ipDetail", false));

    @Override
    public void refreshIpDetailAsync(Long id) {
        executor.execute(() -> {
            User user = userDao.getById(id);
            IpInfo ipInfo = user.getIpInfo();

            if (Objects.isNull(ipInfo)) {
                return;
            }

            // 判断是否需要刷新ip详情
            String ip = ipInfo.needRefreshIpDetail();

            if (StringUtils.isBlank(ip)) {
                return;
            }

            IpDetail ipDetail = tryGetIpDetailOrNullTreeTimes(ip);

            if (Objects.nonNull(ipDetail)) {
                ipInfo.refreshIpDetail(ipDetail);
                User update = new User();
                update.setId(id);
                update.setIpInfo(ipInfo);
                userDao.updateById(update);
            }
        });
    }


    /**
     * 尝试获取指定IP的IpDetail信息，若获取失败，最多重试3次，每次间隔2秒。
     *
     * @param ip 待获取IpDetail信息的IP地址
     * @return 返回指定IP的IpDetail信息，若获取失败则返回null
     */
    private static IpDetail tryGetIpDetailOrNullTreeTimes(String ip) {
        for (int i = 0; i < 3; i++) {
            IpDetail ipDetail = getIpDetailOrNull(ip);
            if (Objects.nonNull(ipDetail)) {
                return ipDetail;
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static IpDetail getIpDetailOrNull(String ip) {
        try {
            String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc");
            ApiResult<IpDetail> result = JSONUtil.toBean(body, new TypeReference<ApiResult<IpDetail>>() {
            }, false);

            return result.getData();
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * 实现线程池的优雅关闭
     * @throws DestroyFailedException
     */
    @Override
    public void destroy() throws InterruptedException {
        executor.shutdown();
        if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {//最多等30秒，处理不完就拉倒
            if (log.isErrorEnabled()) {
                log.error("Timed out while waiting for executor [{}] to terminate", executor);
            }
        }
    }

    public static void main(String[] args) {
        Date begin = new Date();
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            executor.execute(() -> {
                IpDetail ipDetail = tryGetIpDetailOrNullTreeTimes("117.85.133.4");
                Date data = null;
                if (Objects.nonNull(ipDetail)) {
                    data = new Date();
                    System.out.printf("第%d次成功，耗时%dms\n", finalI, data.getTime() - begin.getTime());
                } else {
                    System.out.printf("第%d次失败\n", finalI);
                }
            });
        }
    }
}
